/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.spring.web.bind;

import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.vacoor.nothing.common.json.Jacksons;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.web.domain.filter.GFR;
import org.vacoor.nothing.web.domain.filter.GroupedFilter;
import org.vacoor.nothing.web.domain.filter.Rule;

/**
 * jqGrid 风格参数解析器
 * TODO 不过 mybatis 不知道该如何处理该类型参数. 求助 (当每个组都存在多表交叉时, (A.ID = 1 AND B.ID = 2) OR (A.NAME = A OR (A.AGE=2 AND B.AGE=3))
 filters={ "groupOp": "AND",
 "groups" : [
 { "groupOp": "OR",
 "rules": [
 { "field": "name", "op": "eq", "data": "England" },
 { "field": "id", "op": "le", "data": "5"}
 ]
 }
 ],
 "rules": [
 { "field": "name", "op": "eq", "data": "Romania" },
 { "field": "id", "op": "le", "data": "1"}
 ]
 }
 *
 * @author vacoor
 */
public class GroupedFilterHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    private static final String DEFAULT_FILTER_PARAMETER_NAME = "filters";
    private static final String DEFAULT_QUALIFIER_DELIMITER = "_";
    private static final GroupedFilter.Op DEFAULT_FALLBACK_GROUP_OP = GroupedFilter.Op.AND;
    private static final Rule.Op DEFAULT_FALLBACK_RULE_OP = Rule.Op.CN;

    private String filterParameterName = DEFAULT_FILTER_PARAMETER_NAME;
    private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER;
    private GroupedFilter.Op fallbackGroupOp = DEFAULT_FALLBACK_GROUP_OP;
    private Rule.Op fallbackRuleOp = DEFAULT_FALLBACK_RULE_OP;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return GroupedFilter.class.equals(parameter.getParameterType());
    }

    @Override
    public GroupedFilter resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String filterJson = webRequest.getParameter(getParameterName(filterParameterName, parameter));

        if (StringUtils.hasText(filterJson)) {
            JsonNode root = Jacksons.readTree(filterJson);
            GroupedFilter group = parseFilters(root);
            return group;
        }
        return null;
    }

    /**
     *
     */
    GroupedFilter parseFilters(JsonNode groupNode) {
        if (!groupNode.isObject()) {
            return null;
        }
        JsonNode rules = groupNode.findValue("rules");
        JsonNode groups = groupNode.findValue("groups");
        GroupedFilter.Op groupOp = GroupedFilter.Op.forName(groupNode.findValue("groupOp").asText());

        groupOp = null != groupOp ? groupOp : getFallbackGroupOp();
        GFR filterReq = GFR.create(groupOp);

        // 处理规则
        if (null != rules && rules.isArray()) {
            for (int i = 0; i < rules.size(); i++) {
                JsonNode rule = rules.get(i);
                if (!rule.isObject()) {
                    continue;
                }
                String field = rule.get("field").asText();
                String value = rule.get("data").asText();
                Rule.Op op = Rule.Op.forName(rule.get("op").asText());

                op = null != op ? op : getFallbackRuleOp();
                filterReq.addRule(Rule.create(field, op, op.wrap(value)));
            }
        }
        // 处理子分组
        if (null != groups && groups.isArray()) {
            for (int i = 0; i < groups.size(); i++) {
                filterReq.getGroups().add(parseFilters(groups.get(i)));
            }
        }
        return filterReq;
    }

    protected String getParameterName(String source, MethodParameter parameter) {
        StringBuilder segment = new StringBuilder();
        Qualifier qualifier;
        if (parameter != null && (qualifier = parameter.getParameterAnnotation(Qualifier.class)) != null) {
            segment.append(qualifier.value());
            segment.append(qualifierDelimiter);
        }
        segment.append(source);
        return segment.toString();
    }

    /* **************************
     *
     * ************************ */

    public String getQualifierDelimiter() {
        return qualifierDelimiter;
    }

    public void setQualifierDelimiter(String qualifierDelimiter) {
        this.qualifierDelimiter = qualifierDelimiter;
    }

    public String getFilterParameterName() {
        return filterParameterName;
    }

    public void setFilterParameterName(String filterParameterName) {
        this.filterParameterName = filterParameterName;
    }

    public GroupedFilter.Op getFallbackGroupOp() {
        return fallbackGroupOp;
    }

    public void setFallbackGroupOp(GroupedFilter.Op fallbackGroupOp) {
        this.fallbackGroupOp = fallbackGroupOp;
    }

    public Rule.Op getFallbackRuleOp() {
        return fallbackRuleOp;
    }

    public void setFallbackRuleOp(Rule.Op fallbackRuleOp) {
        this.fallbackRuleOp = fallbackRuleOp;
    }
}
